import logging
import os.path
from logging import handlers
import json
from datetime import datetime
from os import PathLike
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from openpyxl.utils import get_column_letter
import pandas as pd
import plotly.figure_factory as ff
import plotly.graph_objects as go

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import base64
import os


class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)


def get_app_log_handler(
        name: str,
        max_bytes: int = 1024 * 1024 * 100,
        backup_count: int = 5,
        base_path: str = '.',
        logger_level=logging.DEBUG):
    app_log = logging.getLogger(name)
    file_path: str | PathLike[str] = os.path.join(base_path, name + ".log")
    rh = handlers.RotatingFileHandler(file_path, str(max_bytes), backup_count)
    dfs = '%Y-%m-%d %H:%M:%S %p'
    fs = (
        '%(traceId)s|%(asctime)s| %(levelname)s|pathname: %(pathname)s|module:'
        ' %(module)s|funcName: %(funcName)s|lineno:'
        ' %(lineno)d|pid: %(process)d|tid: %(thread)d|msg: %(message)s')
    app_format = logging.Formatter(fs, dfs)
    rh.setFormatter(app_format)
    app_log.addHandler(rh)
    app_log.setLevel(logger_level)
    return app_log


def to_dict(**kwargs):
    return kwargs


def apply_formatting(worksheet, df):
    """
    Apply formatting to the worksheet based on the given DataFrame, including alternating row colors.

    :param worksheet: The worksheet to apply formatting to.
    :param df: The DataFrame used to determine the formatting.
    """
    header_font = Font(bold=True)
    header_alignment = Alignment(horizontal='center', vertical='center')
    header_border = Border(
        left=Side(style='thin'), right=Side(style='thin'),
        top=Side(style='thin'), bottom=Side(style='thin')
    )

    even_fill = PatternFill(start_color="D9EAD3", end_color="D9EAD3", fill_type="solid")
    odd_fill = PatternFill(start_color="FFFFFF", end_color="FFFFFF", fill_type="solid")

    for col_num, col in enumerate(df.columns, 1):
        cell = worksheet.cell(row=1, column=col_num)
        cell.font = header_font
        cell.alignment = header_alignment
        cell.border = header_border
        column_width = max(df[col].astype(str).map(len).max(), len(col)) + 2
        worksheet.column_dimensions[get_column_letter(col_num)].width = column_width

    for row_idx, row in enumerate(
            worksheet.iter_rows(min_row=2, max_row=worksheet.max_row, min_col=1, max_col=worksheet.max_column),
            start=2):
        for cell in row:
            cell.alignment = Alignment(horizontal='left', vertical='center')
            cell.border = Border(
                left=Side(style='thin'), right=Side(style='thin'),
                top=Side(style='thin'), bottom=Side(style='thin')
            )
            if row_idx % 2 == 0:
                cell.fill = even_fill
            else:
                cell.fill = odd_fill


def box_type_image():
    # 创建数据字典
    data = {
        "长度": [
            "20英尺", "20英尺", "20英尺", "20英尺", "20英尺", "20英尺", "20英尺", "20英尺", "40英尺", "40英尺",
            "40英尺", "40英尺", "40英尺", "40英尺", "40英尺", "40英尺", "45英尺", "45英尺", "45英尺", "45英尺",
            "45英尺", "45英尺", "45英尺", "45英尺"],
        "箱型": [
            "干货箱", "干货高箱", "挂衣箱", "开顶箱", "冷冻箱", "冷高箱", "油罐箱", "框架箱", "干货箱", "干货高箱",
            "挂衣箱", "开顶箱", "冷冻箱", "冷高箱", "油罐箱", "框架箱", "干货箱", "干货高箱", "挂衣箱", "开顶箱",
            "冷冻箱", "冷高箱", "油罐箱", "框架箱"],
        "箱型代码": [
            "GP", "GH(HC/HQ)", "HT", "OT", "RF", "RH", "TK", "FR", "GP", "GH(HC/HQ)", "HT", "OT", "RF", "RH",
            "TK", "FR", "GP", "GH(HC/HQ)", "HT", "OT", "RF", "RH", "TK", "FR"],
        "95码": [
            "22G1", "25G1", "22V1", "22U1", "22R1", "25R1", "22T1", "22P1", "42G1", "45G1", "42V1", "42U1",
            "42R1", "45R1", "42T1", "42P1", "L2G1", "L5G1", "L2V1", "L2U1", "L2R1", "L5R1", "L2T1", "L2P1"],
        "英文全称": [
            "General Purpose", "High Container/High Cubic", "Hanger Tanker", "Open Top", "Refrigerated",
            "Refrigerated High", "Tank", "Frame", "General Purpose", "High Container/High Cubic",
            "Hanger Tanker", "Open Top", "Refrigerated", "Refrigerated High", "Tank", "Frame",
            "General Purpose", "High Container/High Cubic", "Hanger Tanker", "Open Top",
            "Refrigerated", "Refrigerated High", "Tank", "Frame"]
    }

    # 创建DataFrame
    df = pd.DataFrame(data)
    fig = ff.create_table(df)
    fig.update_layout(title="箱型代码对照表", font_size=10, width=800, height=450)
    fig.write_image("箱型代码对照表.png", scale=2)


def show_plotly_image_demo():
    # 数据配置
    data = dict(
        labels=["鸿瑞港通", "场内", "20尺堆区", "橙桥", "20英尺|干货箱", "武经理", "20英尺|冷冻箱", "20英尺|干货箱",
                "20英尺|开顶箱", "邓先生", "20英尺|干货箱", "40尺堆区", "将定", "40英尺|干货高箱", "40英尺|开顶箱",
                "橙桥", "40英尺|干货高箱", "武经理", "40英尺|冷高箱", "40英尺|干货箱", "40英尺|干货高箱",
                "45尺堆区", "武经理", "45英尺|冷冻箱", "45英尺|干货箱", "场外", "外租", "武经理",
                "20英尺|干货箱", "40英尺|干货高箱", "45英尺|干货箱", "寄存外场", "将定", "40英尺|干货高箱",
                "武经理", "40英尺|干货高箱", "45英尺|干货箱", "已经售出", "武经理", "20英尺|干货箱",
                "40英尺|干货高箱"],
        parents=["", "鸿瑞港通", "鸿瑞港通/场内", "鸿瑞港通/场内/20尺堆区", "鸿瑞港通/场内/20尺堆区/橙桥",
                 "鸿瑞港通/场内/20尺堆区",
                 "鸿瑞港通/场内/20尺堆区/武经理", "鸿瑞港通/场内/20尺堆区/武经理", "鸿瑞港通/场内/20尺堆区/武经理",
                 "鸿瑞港通/场内/20尺堆区",
                 "鸿瑞港通/场内/20尺堆区/邓先生", "鸿瑞港通/场内", "鸿瑞港通/场内/40尺堆区",
                 "鸿瑞港通/场内/40尺堆区/将定",
                 "鸿瑞港通/场内/40尺堆区/将定", "鸿瑞港通/场内/40尺堆区", "鸿瑞港通/场内/40尺堆区/橙桥",
                 "鸿瑞港通/场内/40尺堆区",
                 "鸿瑞港通/场内/40尺堆区/武经理", "鸿瑞港通/场内/40尺堆区/武经理", "鸿瑞港通/场内/40尺堆区/武经理",
                 "鸿瑞港通/场内", "鸿瑞港通/场内/45尺堆区", "鸿瑞港通/场内/45尺堆区/武经理",
                 "鸿瑞港通/场内/45尺堆区/武经理",
                 "鸿瑞港通", "鸿瑞港通/场外", "鸿瑞港通/场外/外租", "鸿瑞港通/场外/外租/武经理",
                 "鸿瑞港通/场外/外租/武经理",
                 "鸿瑞港通/场外/外租/武经理", "鸿瑞港通/场外", "鸿瑞港通/场外/寄存外场", "鸿瑞港通/场外/寄存外场/将定",
                 "鸿瑞港通/场外/寄存外场", "鸿瑞港通/场外/寄存外场/武经理", "鸿瑞港通/场外/寄存外场/武经理",
                 "鸿瑞港通/场外",
                 "鸿瑞港通/场外/已经售出", "鸿瑞港通/场外/已经售出/武经理", "鸿瑞港通/场外/已经售出/武经理"],
        values=[187, 176, 87, 7, 7, 72, 6, 50, 16, 8, 8, 86, 9, 5, 4, 6, 6, 71, 41, 16, 14, 3, 3, 2, 1, 11, 4, 4, 2, 1,
                1, 5, 1, 1, 4, 2, 2, 2, 2, 1, 1]
    )

    # 绘制旭日图
    fig = go.Figure(go.Sunburst(
        labels=data['labels'],
        parents=data['parents'],
        values=data['values'],
        outsidetextfont={"size": 20, "color": "#377eb8"},
        leaf={"opacity": 0.4},
        marker={"line": {"width": 2}}
    ))

    # 配置布局，确保图表是圆形的
    fig.update_layout(margin=dict(t=0, l=0, r=0, b=0))

    # 显示图表
    fig.show()


def encrypt(algorithm: str, secret_key: bytes, plaintext: str) -> str:
    # Generate a random IV (Initialization Vector)
    iv = os.urandom(16)

    # Create a cipher object
    if algorithm.lower() == 'aes':
        cipher = Cipher(algorithms.AES(secret_key), modes.CBC(iv), backend=default_backend())
    elif algorithm.lower() == 'chacha20':
        cipher = Cipher(algorithms.ChaCha20(secret_key, iv), mode=None, backend=default_backend())
    else:
        raise ValueError("Unsupported algorithm")

    encryptor = cipher.encryptor()

    # Pad the plaintext to be compatible with the block size (only for AES)
    if algorithm.lower() == 'aes':
        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(plaintext.encode()) + padder.finalize()
    else:
        padded_data = plaintext.encode()

    # Encrypt the data
    ciphertext = encryptor.update(padded_data) + encryptor.finalize()

    # Encode the IV and ciphertext using base64
    iv_encoded = base64.b64encode(iv).decode('utf-8')
    ciphertext_encoded = base64.b64encode(ciphertext).decode('utf-8')

    return f"{iv_encoded}:{ciphertext_encoded}"


def decrypt(algorithm: str, secret_key: bytes, encrypted_data: str) -> str:
    # Split the IV and ciphertext
    iv_encoded, ciphertext_encoded = encrypted_data.split(':')

    # Decode the IV and ciphertext from base64
    iv = base64.b64decode(iv_encoded)
    ciphertext = base64.b64decode(ciphertext_encoded)

    # Create a cipher object
    if algorithm.lower() == 'aes':
        cipher = Cipher(algorithms.AES(secret_key), modes.CBC(iv), backend=default_backend())
    elif algorithm.lower() == 'chacha20':
        cipher = Cipher(algorithms.ChaCha20(secret_key, iv), mode=None, backend=default_backend())
    else:
        raise ValueError("Unsupported algorithm")

    decryptor = cipher.decryptor()

    # Decrypt the data
    padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()

    # Unpad the plaintext (only for AES)
    if algorithm.lower() == 'aes':
        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
    else:
        plaintext = padded_plaintext

    return plaintext.decode('utf-8')


def cryptography_demo():
    def get_32_bytes_from_secret_key() -> bytes:
        """
        获取32字节的密钥。
        :return: 32字节密钥
        """
        tmp_key = "1qazxsw23edcvfr45tgbnhy6" * 32
        tmp_key = tmp_key[:32]
        return tmp_key.encode()

    # Example usage
    secret_key = get_32_bytes_from_secret_key()  # Generate a random 256-bit key for AES or ChaCha20
    plaintext = "987d49d30ec30ef7c02944d0c4ac6bd4"

    # Encrypt and decrypt using AES
    encrypted_aes = encrypt('aes', secret_key, plaintext)
    print(f"Encrypted (AES): {encrypted_aes}")

    decrypted_aes = decrypt('aes', secret_key, encrypted_aes)
    print(f"Decrypted (AES): {decrypted_aes}")

    # Encrypt and decrypt using ChaCha20
    encrypted_chacha20 = encrypt('chacha20', secret_key, plaintext)
    print(f"Encrypted (ChaCha20): {encrypted_chacha20}")

    decrypted_chacha20 = decrypt('chacha20', secret_key, encrypted_chacha20)
    print(f"Decrypted (ChaCha20): {decrypted_chacha20}")


def get_logger_with_trace_id(trace_id: str, logger: logging.Logger) -> logging.LoggerAdapter:
    """
    生成带有traceId的LoggerAdapter。

    :param trace_id: 请求的traceId
    :param logger: 原始日志记录器
    :return: 带有traceId的LoggerAdapter
    """
    return logging.LoggerAdapter(logger, {"traceId": trace_id})
